package com.neusoft.neuiotms.dcom.config;

import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.neusoft.bizcore.activiti.dto.WorkorderDTO;
import com.neusoft.bizcore.activiti.model.WorkOrder;
import com.neusoft.bizcore.activiti.repository.WorkOrderRepository;
import com.neusoft.bizcore.activiti.service.WorkOrderService;
import com.neusoft.bizcore.notice.constant.NoticeConsts;
import com.neusoft.bizcore.notice.notification.dto.NoticeDingDTO;
import com.neusoft.bizcore.notice.notification.dto.NoticeRecordDTO;
import com.neusoft.bizcore.notice.notification.dto.NoticeRecordSystemDTO;
import com.neusoft.bizcore.web.support.Searchable;
import com.neusoft.bizcore.web.utils.JsonUtils;
import com.neusoft.neuiotms.dcom.dto.ProjectDTO;
import com.neusoft.neuiotms.dcom.model.Institutions;
import com.neusoft.neuiotms.dcom.model.OperationGroup;
import com.neusoft.neuiotms.dcom.model.ResidentUser;
import com.neusoft.neuiotms.dcom.model.malfunction.MalfunctionApplyForm;
import com.neusoft.neuiotms.dcom.model.malfunction.MalfunctionFrontlineProcessingForm;
import com.neusoft.neuiotms.dcom.model.malfunction.MalfunctionLeaderApproval;
import com.neusoft.neuiotms.dcom.model.malfunction.MalfunctionSecondLine;
import com.neusoft.neuiotms.dcom.repository.InstitutionsRepository;
import com.neusoft.neuiotms.dcom.repository.OperationGroupRepository;
import com.neusoft.neuiotms.dcom.repository.malfunction.MalfunctionApplyFormRepository;
import com.neusoft.neuiotms.dcom.repository.malfunction.MalfunctionFrontlineProcessingFormRepository;
import com.neusoft.neuiotms.dcom.repository.malfunction.MalfunctionLeaderApprovalRepository;
import com.neusoft.neuiotms.dcom.repository.malfunction.MalfunctionSecondLineRepository;
import com.neusoft.neuiotms.dcom.service.ProjectService;
import com.neusoft.neuiotms.micro.notice.NoticeMicroService;

import lombok.extern.slf4j.Slf4j;

/**
 * 故障工单，催单检查任务
 *
 * @author sunchf
 */
@Slf4j
@Configuration
@EnableScheduling
public class ScheduledPromptConfiguration {

    @Autowired
    private WorkOrderRepository workOrderRepository;
    @Autowired
    private MalfunctionApplyFormRepository malfunctionApplyFormRepository;
    @Autowired
    private MalfunctionFrontlineProcessingFormRepository malfunctionFrontlineProcessingFormRepository;
    @Autowired
    private MalfunctionLeaderApprovalRepository malfunctionLeaderApprovalRepository;
    @Autowired
    private MalfunctionSecondLineRepository malfunctionSecondLineRepository;
    @Autowired
    private InstitutionsRepository institutionsRepository;
    @Autowired
    private OperationGroupRepository operationGroupRepository;
    @Autowired
    private ProjectService projectService;
    @Autowired
    private NoticeMicroService noticeMicroService;
    @Autowired
    private WorkOrderService workOrderService;
    @Autowired
    private TaskService taskService;

    // 每5分钟检查一次
    @Scheduled(cron = "0 0/5 * * * ?")
    public void run() {
        ScheduledPromptConfiguration.log.info("scheduled to check malfunction work order every 5 minutes.");
        final ProjectDTO projectInfo = this.projectService.getInfoByCode("HN");
        final Searchable query = new Searchable();
        // 检查运行中的工单
        query.put("type", "malfunction");
        query.put("status", "running");
        final List<WorkOrder> orders = this.workOrderRepository.search(query);
        for (final WorkOrder order : orders) {
            try {

                final String workNum = order.getWorkOrderNum();
                final MalfunctionApplyForm applyForm =
                        this.malfunctionApplyFormRepository.findTopByWorkNumOrderByIdDesc(workNum);

                // 先判断流程是否处于一线工程师导入客户评价和反馈节点，如果是，停止催单
                final WorkorderDTO workForm = this.workOrderService.details(workNum);
                final String instanceId = workForm.getInstanceId();
                final List<Task> tasks = this.taskService.createTaskQuery().processInstanceId(instanceId).list();
                if (tasks.stream().filter(it -> "MALFUNCTIONCUSTOMERREVIEWFORM".equals(it.getFormKey())).findAny()
                        .isPresent()) {
                    return;
                }

                // 一线工程师处理后，才能知道故障级别，如果未解决，就根据催单时长判断催单
                final MalfunctionFrontlineProcessingForm frontlineProcessingForm =
                        this.malfunctionFrontlineProcessingFormRepository.findTopByWorkNumOrderByIdDesc(workNum);
                if (null != frontlineProcessingForm) {
                    // 有了前面当前流程节点位置的判断，就不需要对一线是否处理做特殊判断了
                    // if (!frontlineProcessingForm.getSolved()) {}

                    // 二线工程师优先级最高，如果存在，按照他催单的故障等级
                    final MalfunctionSecondLine malfunctionSecondLine =
                            this.malfunctionSecondLineRepository.findTopByWorkNumOrderByIdDesc(workNum);
                    if ((null != malfunctionSecondLine)
                            && StringUtils.isNotBlank(malfunctionSecondLine.getFaultLevel())) {
                        final String failureLevelCode = malfunctionSecondLine.getFaultLevel();
                        this.shouldPrompt(workNum, failureLevelCode, projectInfo, applyForm.getUpdateTime().getTime());
                    } else {
                        // 一线组长优先级次之，如果存在，按照他催单的故障等级
                        final MalfunctionLeaderApproval malfunctionLeaderApproval =
                                this.malfunctionLeaderApprovalRepository.findTopByWorkNumOrderByIdDesc(workNum);
                        if ((null != malfunctionLeaderApproval)
                                && StringUtils.isNotBlank(malfunctionLeaderApproval.getFaultLevel())) {
                            final String failureLevelCode = malfunctionLeaderApproval.getFaultLevel();
                            this.shouldPrompt(workNum, failureLevelCode, projectInfo,
                                    applyForm.getUpdateTime().getTime());
                        } else {
                            // 最后，按照一线工程师的故障等级判断是否催单
                            final String failureLevelCode = frontlineProcessingForm.getFailureLevelCode();
                            this.shouldPrompt(workNum, failureLevelCode, projectInfo,
                                    applyForm.getUpdateTime().getTime());
                        }
                    }
                } else {

                    // 一线工程师还未处理的工单，默认按照一般故障级别催单
                    this.shouldPrompt(workNum, applyForm.getServiceUnitCode(), projectInfo,
                            applyForm.getUpdateTime().getTime());
                }
            } catch (final Exception e) {
                ScheduledPromptConfiguration.log.error("", e);
            }
        }

    }

    private void shouldPrompt(final String workNum, String failureLevelCode, ProjectDTO projectInfo,
            final long start) {
        long promptInteval = projectInfo.getSlightPromptInteval();
        if ("slight".equals(failureLevelCode)) {
            promptInteval = projectInfo.getSlightPromptInteval();
        } else if ("general".equals(failureLevelCode)) {
            promptInteval = projectInfo.getGeneralPromptInteval();
        } else if ("serious".equals(failureLevelCode)) {
            promptInteval = projectInfo.getSeriousPromptInteval();
        }

        if (this.shouldPrompt(start,
                promptInteval)) {
            this.prompt(workNum);
        }
    }

    /**
     * @param start 工单创建时间，时间戳
     * @param promptInterval 故障级别催单时长，分钟单位
     * @return
     */
    private boolean shouldPrompt(final long start, final long promptInterval) {
        final Calendar cal = Calendar.getInstance();
        final long elapsedTime = cal.getTimeInMillis() - start;
        // 5分钟的提前量
        if (elapsedTime > ((promptInterval - 5) * 60 * 1000)) {
            return true;
        }
        return false;
    }

    private void prompt(final String workNum) {
        final MalfunctionApplyForm applyForm =
                this.malfunctionApplyFormRepository.findTopByWorkNumOrderByIdDesc(workNum);
        if (null != applyForm) {
            this.prompt(workNum, applyForm.getServiceUnitCode());
        }
    }

    private void prompt(final String workNum, final String instituteCode) {
        final Institutions inst = this.institutionsRepository.findByCode(instituteCode);
        if (null == inst) {
            return;
        }

        final List<String> userArray = Lists.newArrayList();
        // 通知驻场人员
        userArray.addAll(inst.getResidents().stream().map(ResidentUser::getUsername).collect(Collectors.toList()));

        final OperationGroup group = this.operationGroupRepository.findByInstitutionsIn(Lists.newArrayList(inst));
        if (null != group) {
            // 通知组长
            userArray.add(group.getGroupLeaderCode());
        }

        if (userArray.size() == 0) {
            return;
        }

        final NoticeRecordDTO notice = new NoticeRecordDTO();
        notice.setCreateTime(new Date());
        notice.setAppCode("dcom");
        notice.setType(Lists.newArrayList(NoticeConsts.INFORM_TYPE_NOTICE, NoticeConsts.INFORM_TYPE_DINGDING));
        notice.setTitle("故障工单处理提醒");
        final NoticeRecordSystemDTO toSystem = new NoticeRecordSystemDTO();
        toSystem.setNoticeType("工单提醒");
        toSystem.setUsers(userArray);
        toSystem.setContent("您负责的厅局" + inst.getName() + ",有未解决的故障工单" + workNum + ",请及时处理提醒");
        notice.setSystem(toSystem);

        final NoticeDingDTO toDing = new NoticeDingDTO();
        toDing.setMsgtype("text");
        toDing.setUsers(userArray);
        final Map<String, String> linkParams = Maps.newHashMap();
        linkParams.put("content", "您负责的厅局" + inst.getName() + ",有未解决的故障工单" + workNum + ",请及时处理提醒");
        toDing.setJsonBody(JsonUtils.pojoToJson(linkParams));
        notice.setDing(toDing);
        this.noticeMicroService.createNotification(JsonUtils.pojoToJson(notice));
        ScheduledPromptConfiguration.log.info("send prompt notice to users: {}", String.join(",", userArray));
    }
}
